package test.base.medium.ffmpeg;

import static org.bytedeco.ffmpeg.global.avcodec.*;

import static org.bytedeco.ffmpeg.global.avformat.*;
import static org.bytedeco.ffmpeg.global.avutil.*;

import static org.bytedeco.ffmpeg.global.swscale.*;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.bytedeco.ffmpeg.avcodec.AVCodec;
import org.bytedeco.ffmpeg.avcodec.AVCodecContext;
import org.bytedeco.ffmpeg.avcodec.AVCodecParameters;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVStream;
import org.bytedeco.ffmpeg.avutil.AVDictionary;
import org.bytedeco.ffmpeg.avutil.AVFrame;
import org.bytedeco.ffmpeg.avutil.AVRational;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.ffmpeg.swscale.SwsContext;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.DoublePointer;
import org.bytedeco.javacpp.Pointer;
import org.bytedeco.javacpp.PointerPointer;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameRecorder.Exception;

public class ACVideo {
	ACBase 输出引擎;
	// 输出格式上下文
	AVFormatContext 输出设备;
	AVStream 输入流;

	// 输出流
	AVStream 输出流 = null;
	// 编码
	AVCodec 编码 = null;
	// 编码器
	AVCodecContext 编码器 = null;
	private SwsContext 图像变形器;
	private int video_outbuf_size;
	private AVPacket video_pkt;
	double 帧率 = 30;
	AVRational av帧率;
	AVRational av时间基线;
	int ret = -1;
	int imageScalingFlags;
	private BytePointer 图像缓存 = null;
	private BytePointer 视频输出缓冲 = null;
	private AVFrame 图像 = null, 临时图像 = null;
	// 多媒体元数据,类似于字段结构
	protected Map<String, String> 自定义元数据 = new HashMap<String, String>();
	protected Map<String, String> 自定义属性 = new HashMap<String, String>();

	public void close() {
		avcodec_free_context(编码器);
	}

	/**
	 * 编码id
	 */

	int codecid = 0;
	ACBase 引擎;

	// 根据输入流的属性构建输出设备
	public ACVideo do1_创建并配置(ACBase 引擎, AVStream 输入流) {
		this.引擎 = 引擎;
		this.输出设备 = 引擎.输出设备;
		this.输入流 = 输入流;
		// 创建一个输出流,用于编解码后输出
		if ((输出流 = avformat_new_stream(输出设备, null)) == null) {
			if (输出流.metadata() != null) {
				av_dict_free(输出流.metadata());
				输出流.metadata(null);
			}
		}
		// 设置编码
		codecid = 输入流.codecpar().codec_id();
		// 发现id匹配的视频编码
		编码 = avcodec_find_encoder(codecid);// 音频为audio_codec
		// 通过编码-->构造编码器
		if ((编码器 = avcodec_alloc_context3(编码)) == null) {// 需要使用avcodec_free_context释放
			avcodec_free_context(编码器);
			编码器 = null;

		}
		if (输入流 == null)
			return null;
		AVCodecParameters inparam = 输入流.codecpar();
		/*
		 * 流的实际基本帧速率。这是所有时间戳的最低帧速率
		 * 
		 * 准确地表示（这是最不常见的倍数流中的帧率）。注意，这个值只是一个猜测！
		 * 
		 * 例如，如果时基是1/90000，并且所有帧都有
		 * 
		 * 大约3600或1800个计时器滴答声，则r_帧速率将为50/1。
		 */
		// if (inpVideoStream.r_frame_rate().num() != AV_NOPTS_VALUE &&
		// inpVideoStream.r_frame_rate().den() != 0) {
		// 帧率 = (inpVideoStream.r_frame_rate().num()) /
		// (inpVideoStream.r_frame_rate().den());
		// }
		av帧率 = 输入流.r_frame_rate();
		/*
		 * 将源AVCodecContext的设置复制到目标 avcodeContext。生成的目标编解码器上下文将是
		 * 未打开，即要求您在调用avcodec_open2（）之前 可以使用此AVcodeContext对视频/音频数据进行解码/编码。
		 */
		avcodec_parameters_to_context(编码器, 输入流.codecpar());
		// av_codec_set_pkt_timebase 已经过时被替换
		// av_codec_set_pkt_timebase(编码器, stream.time_base());
		// 将源AVCodecContext的设置复制到目标avcodeContext。生成的目标编解码器上下文将是未打开，即要求您在调用avcodec_open2（）之前
		// avcodec_copy_context(输出流.codec(), inpVideoStream.codec()
		// avcodec_copy_context 替换成avcodec_parameters_to_context
		// 像素格式 如果从报头知道，则可以由解复用器设置。

		// 仅限视频。单个像素的纵横比（宽/高）显示时应该有。
		// 当宽高比未知/未定义时，分子应为设置为0（分母可以有任何值）。
		AVRational 宽高比 = inparam.sample_aspect_ratio();
		编码器.sample_aspect_ratio(宽高比);
		输出流.sample_aspect_ratio(宽高比);
		// 编码时使用,解码不用不能按帧更改的编解码器的全局质量。
		// *这应该与MPEG-1/2/4的qscale成比例。
//		int 视频质量 = inpVideoStream.codec().global_quality(); // .global_quality();
//		if (视频质量 >= 0) {
//			编码器.flags(编码器.flags() | AV_CODEC_FLAG_QSCALE);
//			编码器.global_quality((int) Math.round(FF_QP2LAMBDA * 视频质量));
//		}
		set帧率(av帧率);
		// 这是用来解决一些编码器的错误。一般不用改动\
		// 编码器.codec_tag(0);inpVideoStream 中有
		// 编码器.codec_id(inparam.codec_id());inpVideoStream 中有
		// 设置编码器类型 视频还是音频,必须是视频
		// 编码器.codec_type(AVMEDIA_TYPE_VIDEO);
		编码器.gop_size(-1);

		return this;
	}

	public void do准备工作缓存区2() {
		do修正参数();
		av时间基线 = avutil.av_inv_q(av帧率);
		编码器.time_base(av时间基线);
		avcodec_parameters_from_context(输出流.codecpar(), 编码器);
		输出流.time_base(av时间基线);
		输出流.avg_frame_rate(av帧率);
		输出流.codec().time_base(av时间基线); // “已弃用”，但这实际上是必需的
		try {
			set工作缓冲区();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public boolean recordImage(int width, int height, int depth, int channels, int stride, int pixelFormat,
			Buffer... image) throws Exception {
		if (输出流 == null) {
			error("输出流为空异常");
		}

		if (!输出引擎.启动) {
			error("程序关闭异常");
		}
		int ret = 0;

		if (image == null || image.length == 0) {
			/*
			 * 不再压缩帧。如果使用B，编解码器的延迟只有几帧*帧，所以我们通过再次传递相同的图片得到最后一帧
			 */
		} else {
			int step = stride * Math.abs(depth) / 8;
			BytePointer data = image[0] instanceof ByteBuffer ? new BytePointer((ByteBuffer) image[0]).position(0)
					: new BytePointer(new Pointer(image[0]).position(0));

			if (pixelFormat == AV_PIX_FMT_NONE) {
				if ((depth == Frame.DEPTH_UBYTE || depth == Frame.DEPTH_BYTE) && channels == 3) {
					pixelFormat = AV_PIX_FMT_BGR24;
				} else if ((depth == Frame.DEPTH_UBYTE || depth == Frame.DEPTH_BYTE) && channels == 1) {
					pixelFormat = AV_PIX_FMT_GRAY8;
				} else if ((depth == Frame.DEPTH_USHORT || depth == Frame.DEPTH_SHORT) && channels == 1) {
					pixelFormat = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN) ? AV_PIX_FMT_GRAY16BE
							: AV_PIX_FMT_GRAY16LE;
				} else if ((depth == Frame.DEPTH_UBYTE || depth == Frame.DEPTH_BYTE) && channels == 4) {
					pixelFormat = AV_PIX_FMT_RGBA;
				} else if ((depth == Frame.DEPTH_UBYTE || depth == Frame.DEPTH_BYTE) && channels == 2) {
					pixelFormat = AV_PIX_FMT_NV21; // Android's camera capture format
				} else {
					throw new Exception("无法猜测图像的像素格式: 深度=" + depth + ", 通道=" + channels);
				}
			}

			if (pixelFormat == AV_PIX_FMT_NV21) {
				step = width;
			}

			if (编码器.pix_fmt() != pixelFormat || 编码器.width() != width || 编码器.height() != height) {
				/* 如果需要 启用图像转换器 */
				图像变形器 = sws_getCachedContext(图像变形器, width, height, pixelFormat, 编码器.width(), 编码器.height(),
						编码器.pix_fmt(), imageScalingFlags != 0 ? imageScalingFlags : SWS_BILINEAR, null, null,
						(DoublePointer) null);
				if (图像变形器 == null) {
					throw new Exception("sws_getCachedContext() error: 不能初始化变形器");
				}
				av_image_fill_arrays(new PointerPointer(临时图像), 临时图像.linesize(), data, pixelFormat, width, height, 1);
				av_image_fill_arrays(new PointerPointer(图像), 图像.linesize(), 图像缓存, 编码器.pix_fmt(), 编码器.width(),
						编码器.height(), 1);
				临时图像.linesize(0, step);
				临时图像.format(pixelFormat);
				临时图像.width(width);
				临时图像.height(height);
				图像.format(编码器.pix_fmt());
				图像.width(编码器.width());
				图像.height(编码器.height());
				sws_scale(图像变形器, new PointerPointer(临时图像), 临时图像.linesize(), 0, height, new PointerPointer(图像),
						图像.linesize());
			} else {
				av_image_fill_arrays(new PointerPointer(图像), 图像.linesize(), data, pixelFormat, width, height, 1);
				图像.linesize(0, step);
				图像.format(pixelFormat);
				图像.width(width);
				图像.height(height);
			}
		}

//	        if ((oformat.flags() & AVFMT_RAWPICTURE) != 0) {
//	            if (image == null || image.length == 0) {
//	                return false;
//	            }
//	            /* raw video case. The API may change slightly in the future for that? */
//	            av_init_packet(video_pkt);
//	            video_pkt.flags(video_pkt.flags() | AV_PKT_FLAG_KEY);
//	            video_pkt.stream_index(输出流.index());
//	            video_pkt.data(new BytePointer(picture));
//	            video_pkt.size(Loader.sizeof(AVFrame.class));
//	        } else {
		/* encode the image */
		av_init_packet(video_pkt);
		// video_pkt.flags(video_pkt.flags() | AV_PKT_FLAG_KEY);
		// video_pkt.stream_index(输出流.index());
		video_pkt.data(视频输出缓冲);
		video_pkt.size(video_outbuf_size);
		图像.quality(编码器.global_quality());
// if ((ret = avcodec_encode_video2(编码器, video_pkt, image == null || image.length == 0 ? null : 图像, got_video_packet)) < 0) {
// }

		if (avcodec_send_frame(编码器, image == null || image.length == 0 ? null : 图像) == 0)
			if (avcodec_receive_packet(编码器, video_pkt) == 0) {
				图像.pts(图像.pts() + 1);

				if (video_pkt.pts() != AV_NOPTS_VALUE) {
					video_pkt.pts(av_rescale_q(video_pkt.pts(), 编码器.time_base(), 输出流.time_base()));
				}
				if (video_pkt.dts() != AV_NOPTS_VALUE) {
					video_pkt.dts(av_rescale_q(video_pkt.dts(), 编码器.time_base(), 输出流.time_base()));
				}
				video_pkt.stream_index(输出流.index());
				video_pkt.flags(video_pkt.flags() | AV_PKT_FLAG_KEY);
				this.输出引擎.writePacket(AVMEDIA_TYPE_VIDEO, video_pkt);
				return true;

			}
		free缓存();
		return false;

	}

	public void set工作缓冲区() throws Exception {

		AVDictionary options = new AVDictionary(null);
//	            if (videoQuality >= 0) {
//	                av_dict_set(options, "crf", "" + videoQuality, 0);
//	            }
		for (Entry<String, String> e : 自定义属性.entrySet()) {
			av_dict_set(options, e.getKey(), e.getValue(), 0);
		}

		// 使用多线程
		编码器.thread_count(0);

		/* open the codec */
		if ((ret = avcodec_open2(编码器, 编码, options)) < 0) {
			av_dict_free(options);
			error("avcodec_open2() error =" + ret + ":不能启动编码器");
		}
		av_dict_free(options);

		视频输出缓冲 = null;
//	            if ((oformat.flags() & AVFMT_RAWPICTURE) == 0) {
		// 视频输出大小=数学.max（256*1024，8*编码器.width（）*编码器.height（））；//一个laffmpeg.c

		// video_outbuff=新的BytePointer（av_malloc（video_outuff_size））；
//	            }

		/* allocate the encoded raw picture */
		if ((图像 = av_frame_alloc()) == null) {
			error("av_frame_alloc() 异常: 不能分配图像空间");
		}
		图像.pts(0); // magic required by libx264

		int size = av_image_get_buffer_size(编码器.pix_fmt(), 编码器.width(), 编码器.height(), 1);
		if ((图像缓存 = new BytePointer(av_malloc(size))).isNull()) {
			error缓存("av_malloc() 异常:不能分配图像缓存");
		}

		/* 如果输出格式不等于图像格式，则图片也是需要的。然后将其转换为所需的输出格式 */
		if ((临时图像 = av_frame_alloc()) == null) {
			error缓存("av_frame_alloc() 异常:不允许分配临时图像空间");
		}

		/* 根据提供的codeccontext中的值填充parameters结构 */
		if ((ret = avcodec_parameters_from_context(输出流.codecpar(), 编码器)) < 0) {
			error缓存("avcodec_parameters_from_context() 异常: 无法拷贝");
		}

		AVDictionary metadata = new AVDictionary(null);
		for (Entry<String, String> e : 自定义元数据.entrySet()) {
			av_dict_set(metadata, e.getKey(), e.getValue(), 0);
		}
		输出流.metadata(metadata);

	}

	public ACVideo set编码(int code) {
		编码器.codec_id(code);
		return this;

	}

	public ACVideo set视频质量(int 视频质量) {
		编码器.flags(编码器.flags() | AV_CODEC_FLAG_QSCALE);
		编码器.global_quality((int) Math.round(FF_QP2LAMBDA * 视频质量));
		return this;

	}

	public ACVideo set比特率(long 比特率) {
		编码器.bit_rate(比特率);
		return this;
	}

	public ACVideo set宽高(int 宽, int 高) {
		/* 分辨率必须是2的倍数。缩放高度以保持纵横比。 */
		if (宽 % 2 == 1) {
			int roundedWidth = 宽 + 1;
			高 = (roundedWidth * 高) / 宽;
			宽 = roundedWidth;
		}
		编码器.width(宽);
		编码器.height(高);
		return this;

	}

	/*
	 * 样本纵横比（未知时为0）
	 * 
	 * 即像素的宽度除以像素的高度。
	 * 
	 * 对于某些视频标准，分子和分母必须相对质数且小于256。
	 */
	public ACVideo set纵横比(double 纵横比) {
		if (纵横比 > 0) {
			// 将双精度浮点数转换为AVRational有理数。
			AVRational r = av_d2q(纵横比, 255);
			编码器.sample_aspect_ratio(r);
			输出流.sample_aspect_ratio(r);
		}
		return this;
	}

	public ACVideo set像素格式(int 像素格式) {
		if (像素格式 != AV_PIX_FMT_NONE) {
			编码器.pix_fmt(像素格式);
		} else if (编码器.codec_id() == AV_CODEC_ID_RAWVIDEO || 编码器.codec_id() == AV_CODEC_ID_PNG
				|| 编码器.codec_id() == AV_CODEC_ID_HUFFYUV || 编码器.codec_id() == AV_CODEC_ID_FFV1) {
			编码器.pix_fmt(AV_PIX_FMT_RGB32); // appropriate for common lossless formats
		} else if (编码器.codec_id() == AV_CODEC_ID_JPEGLS) {
			编码器.pix_fmt(AV_PIX_FMT_BGR24);
		} else if (编码器.codec_id() == AV_CODEC_ID_MJPEG || 编码器.codec_id() == AV_CODEC_ID_MJPEGB) {
			编码器.pix_fmt(AV_PIX_FMT_YUVJ420P);
		} else {
			编码器.pix_fmt(AV_PIX_FMT_YUV420P); // lossy, but works with about everything
		}
		return this;
	}

	public ACVideo set帧率(double 帧率) {
		av帧率 = av_d2q(帧率, 1001000);
		return set帧率(av帧率);
	}

	public ACVideo set帧率(AVRational 帧率) {
		// av帧率 = av_d2q(帧率, 1001000);
		AVRational supported_framerates = 编码.supported_framerates();
		if (supported_framerates != null) {
			int idx = av_find_nearest_q_idx(av帧率, supported_framerates);
			av帧率 = supported_framerates.position(idx);
		}

		return this;
	}

	public ACVideo set帧组(int 帧组) {
		if (帧组 >= 0) {
			编码器.gop_size(帧组); /* 每帧最多发射一个帧内帧 */
		}
		return this;
	}

	public ACVideo set质量(int 视频质量) {
		if (视频质量 >= 0) {
			编码器.flags(编码器.flags() | AV_CODEC_FLAG_QSCALE);
			编码器.global_quality((int) Math.round(FF_QP2LAMBDA * 视频质量));
		}
		return this;
	}

	public ACVideo do修正参数() {
		if (编码器.codec_id() == AV_CODEC_ID_MPEG2VIDEO) {
			/* 为了测试，我们还添加了B帧 */
			编码器.max_b_frames(2);
		} else if (编码器.codec_id() == AV_CODEC_ID_MPEG1VIDEO) {

			/*
			 * 需要避免使用某些系数溢出的宏块。
			 * 
			 * 正常的视频不会发生这种情况，只是在这里
			 * 
			 * 色度平面的运动与亮度平面不匹配。
			 */
			编码器.mb_decision(2);
		} else if (编码器.codec_id() == AV_CODEC_ID_H263) {
			int 宽 = 编码器.width(), 高 = 编码器.height();

			// H263不支持以下任何其他决议
			if (宽 <= 128 && 高 <= 96) {
				编码器.width(128).height(96);
			} else if (宽 <= 176 && 高 <= 144) {
				编码器.width(176).height(144);
			} else if (宽 <= 352 && 高 <= 288) {
				编码器.width(352).height(288);
			} else if (宽 <= 704 && 高 <= 576) {
				编码器.width(704).height(576);
			} else {
				编码器.width(1408).height(1152);
			}
		} else if (编码器.codec_id() == AV_CODEC_ID_H264) {
			// 默认为受约束的基线，以生成在任何内容上播放的内容，
			// 对于大多数用例没有任何重要的权衡
			编码器.profile(AVCodecContext.FF_PROFILE_H264_CONSTRAINED_BASELINE);
		}
		return this;
	}

	public ACVideo set最大B帧(int 最大b帧) {
		if (最大b帧 >= 0) {
			编码器.max_b_frames(最大b帧);
			编码器.has_b_frames(最大b帧 == 0 ? 0 : 1);
		}
		return this;
	}

	// 有些格式希望流标题是分开的
	/**
	 * 如果AVCodecContext中设置了AV_CODEC_FLAG_GLOBAL_HEADER标志，<br>
	 * 对于生成的视频文件（如mp4），在windows操作系统下，并以图标的方式查看该视频文件时，<br>
	 * 视频文件的图标将是视频文件的第一帧，从而起到预览的作用<br>
	 * 
	 * 设置该标志位，在解码过程中似乎并没有任何的效果，<br>
	 * 本意是通过设置该标志位AV_CODEC_FLAG_GLOBAL_HEADER<br>
	 * 通过AVCodecContext在解码过程中，从extradata中读取SPS/PPS，<br>
	 * 然后将信息填充到每一帧之前，然后正确解码<br>
	 * 
	 * @param 允许预览帧
	 * @return
	 */
	public ACVideo enable头帧() {
		if ((编码器.flags() & AVFMT_GLOBALHEADER) != 0) {
			编码器.flags(编码器.flags() | AV_CODEC_FLAG_GLOBAL_HEADER);
		}
		return this;
	}

	public ACVideo enable非标编码() {

		if ((编码.capabilities() & AV_CODEC_CAP_EXPERIMENTAL) == AV_CODEC_CAP_EXPERIMENTAL) {
			// 允许非标准化的实验 意味着编码器可能会产生不受支持的输出
			编码器.strict_std_compliance(AVCodecContext.FF_COMPLIANCE_EXPERIMENTAL);
		}
		return this;
	}

	public ACVideo do优化速率失真(int 失真格子) {
		if (失真格子 >= 0) {
			编码器.trellis(失真格子);
		}
		return this;
	}

	public int getImageScalingFlags() {
		return imageScalingFlags;
	}

	public void setImageScalingFlags(int imageScalingFlags) {
		this.imageScalingFlags = imageScalingFlags;
	}

	public void error(String 异常) throws Exception {
		free();
		throw new Exception(异常);
	}

	public void error缓存(String 异常) throws Exception {
		free缓存();
		throw new Exception(异常);
	}

	public void free缓存() {
		if (图像缓存 != null) {
			av_free(图像缓存);
			图像缓存 = null;
		}
		if (图像 != null) {
			av_frame_free(图像);
			图像 = null;
		}
		if (临时图像 != null) {
			av_frame_free(临时图像);
			临时图像 = null;
		}
		if (视频输出缓冲 != null) {
			av_free(视频输出缓冲);
			视频输出缓冲 = null;
		}
	}

	public void free() {

		free缓存();
		if (编码器 != null) {
			avcodec_free_context(编码器);
			编码器 = null;
		}

		if (输出流 != null && 输出流.metadata() != null) {
			av_dict_free(输出流.metadata());
			输出流.metadata(null);
		}
		if (图像变形器 != null) {
			sws_freeContext(图像变形器);
			图像变形器 = null;
		}
	}

}

// 该函数avcodec_parameters_to_context 已经完成了大部分的赋值工作,不需要自己再赋值,除非有修改
// 事实上codecpar包含了大部分解码器相关的信息，这里是直接从AVCodecParameters复制到AVCodecContext
// codec->codec_type = par->codec_type;//AVMEDIA_TYPE_VIDEO
// codec->codec_id = par->codec_id;//AV_CODEC_ID_H264
// codec->codec_tag = par->codec_tag;//0
//
// codec->bit_rate = par->bit_rate;//0
// codec->bits_per_coded_sample = par->bits_per_coded_sample;//0
// codec->bits_per_raw_sample = par->bits_per_raw_sample;//8
// codec->profile = par->profile;//66
// codec->level = par->level;//42
//
// switch (par->codec_type) {
// case AVMEDIA_TYPE_VIDEO:
// codec->pix_fmt = par->format;//12
// codec->width = par->width;//1920
// codec->height = par->height;//1080
// codec->field_order = par->field_order;//AV_FIELD_PROGRESSIVE
// codec->color_range = par->color_range;//AVCOL_RANGE_JPEG
// codec->color_primaries = par->color_primaries;//AVCOL_PRI_BT709
// codec->color_trc = par->color_trc;//AVCOL_TRC_BT709
// codec->colorspace = par->color_space;//AVCOL_SPC_BT709
// codec->chroma_sample_location = par->chroma_location;//AVCHROMA_LOC_LEFT
// codec->sample_aspect_ratio = par->sample_aspect_ratio;//num=0,den=1
// codec->has_b_frames = par->video_delay;//0
// break;
// case AVMEDIA_TYPE_AUDIO:
// codec->sample_fmt = par->format;
// codec->channel_layout = par->channel_layout;
// codec->channels = par->channels;
// codec->sample_rate = par->sample_rate;
// codec->block_align = par->block_align;
// codec->frame_size = par->frame_size;
// codec->delay =
// codec->initial_padding = par->initial_padding;
// codec->trailing_padding = par->trailing_padding;
// codec->seek_preroll = par->seek_preroll;
// break;
// case AVMEDIA_TYPE_SUBTITLE:
// codec->width = par->width;
// codec->height = par->height;
// break;
// }
